#include "stdafx.h"
#include "..\..\cz_def.h"
#include "..\..\function\cz_log.h"
#include "file_def.h"
#include "FileReaderMapping.h"

#define MIN_USE_MEMORY						1  * 1024 * 1024
#define MAX_USE_MEMORY						64 * 1024 * 1024

FileReaderMapping::FileReaderMapping()
{
	Clear();
}

FileReaderMapping::~FileReaderMapping()
{
	Uninit();
}

bool FileReaderMapping::Init(IN stringc& strFile, IN u32 useMemory, IN u32 reservedSpace, IN u32 readFlag)
{
	SYSTEM_INFO si;
	DWORD high, low;

	Uninit();

	m_useMemory = useMemory;
	m_reservedSpace = reservedSpace;
	m_readFlag = readFlag;

	m_hFile = CreateFileA(strFile.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (m_hFile == INVALID_HANDLE_VALUE)
	{
		CZ_LOG_ERRORA("");
		goto L_Failed;
	}

	if (GetFileSizeEx(m_hFile, (LARGE_INTEGER*)&m_fileSize) == FALSE)
	{
		CZ_LOG_ERRORA("");
		goto L_Failed;
	}
	
	if (m_fileSize != 0)
	{
		high = ((LARGE_INTEGER*)&m_fileSize)->HighPart;
		low = ((LARGE_INTEGER*)&m_fileSize)->LowPart;

		m_hMapping = CreateFileMapping(m_hFile, NULL, PAGE_READONLY, high, low, NULL);
		if (m_hMapping == NULL)
		{
			CZ_LOG_ERRORA("");
			goto L_Failed;
		}
	}

	GetSystemInfo(&si);
	m_grain = si.dwAllocationGranularity;

	return true;

L_Failed:
	Uninit();
	return false;
}

void FileReaderMapping::Uninit()
{
	if (m_view != NULL) UnmapViewOfFile(m_view);
	if (m_hMapping != NULL) CloseHandle(m_hMapping);
	if (m_hFile != INVALID_HANDLE_VALUE) CloseHandle(m_hFile);

	Clear();
}

u64 FileReaderMapping::GetFileSize()
{
	return m_fileSize;
}

bool FileReaderMapping::GetData(IN u64 offset, OUT u8* data, IN u32 size)
{
	if (size == 0) return true;

	if (!CheckSize(0, m_fileSize, offset, size))
	{
		CZ_LOG_ERRORA("");
		return false;
	}

	if (!CheckSize(m_fileOffset, m_fillSize, offset, size))
	{
		if (!ReMapping(offset, size))
		{
			if (!ReadFileDirect(offset, data, size))
			{
				CZ_LOG_ERRORA("");
				return false;
			}
			return true;
		}
	}
	
	memcpy(data, m_view + offset - m_fileOffset, size);

	return true;
}

bool FileReaderMapping::Getu64(IN u64 offset, OUT u64& v)
{
	return GetData(offset, (u8*)&v, 8);
}

bool FileReaderMapping::Getu32(IN u64 offset, OUT u32& v)
{
	return GetData(offset, (u8*)&v, 4);
}

bool FileReaderMapping::Getu16(IN u64 offset, OUT u16& v)
{
	return GetData(offset, (u8*)&v, 2);
}

bool FileReaderMapping::Getu8(IN u64 offset, OUT u8& v)
{
	return GetData(offset, &v, 1);
}

void FileReaderMapping::SetReservedSpace(IN u32 reservedSpace)
{
	m_reservedSpace = reservedSpace;
}

void FileReaderMapping::Clear()
{
	m_useMemory = 0;
	m_reservedSpace = 0;
	m_readFlag = READ_FLAG_NULL;

	m_hFile = INVALID_HANDLE_VALUE;
	m_hMapping = NULL;
	m_fileSize = 0;
	m_grain = 0;

	m_view = NULL;
	m_fileOffset = 0;
	m_fillSize = 0;
}

void FileReaderMapping::CheckParameter()
{
	if (m_useMemory < MIN_USE_MEMORY) m_useMemory = MIN_USE_MEMORY;
	else if (m_useMemory > MAX_USE_MEMORY) m_useMemory = MAX_USE_MEMORY;

	if (((m_readFlag & READ_FLAG_FORWARD) != 0) &&
		((m_readFlag & READ_FLAG_BACKWARD) != 0)) m_readFlag &= ~READ_FLAG_BACKWARD;
	else if (((m_readFlag & READ_FLAG_FORWARD) == 0) &&
		((m_readFlag & READ_FLAG_BACKWARD) == 0)) m_readFlag |= READ_FLAG_FORWARD;
}

bool FileReaderMapping::ReMapping(IN u64 offset, IN u32 size)
{
	DWORD high, low;
	u64 tempFileOffset, t1, t2, t3;
	u32 tempFillSize;

	if (size > m_useMemory)
	{
		CZ_LOG_ERRORA("");
		return false;
	}

	// calculate fileOffset & fillSize
	t1 = offset + size - min(offset + size, m_useMemory);
	if ((m_readFlag & READ_FLAG_FORWARD) != 0)
	{
		t2 = min(offset, m_reservedSpace);
		t2 = offset - min(m_useMemory, t2);
		if (t2 >= t1)
		{
			t3 = t2 / m_grain * m_grain;
			if (t3 >= t1) tempFileOffset = t3;
			else 
			{
				t3 = (t2 + m_grain - 1) / m_grain * m_grain;
				if (t3 <= offset) tempFileOffset = t3;
				else
				{
					CZ_LOG_ERRORA("");
					return false;
				}
			}
		}
		else
		{
			t3 = (t1 + m_grain - 1) / m_grain * m_grain;
			if (t3 <= offset) tempFileOffset = t3;
			else
			{
				CZ_LOG_ERRORA("");
				return false;
			}
		}
	}
	else if ((m_readFlag & READ_FLAG_BACKWARD) != 0)
	{
		t2 = min(m_fileSize - offset, m_reservedSpace);
		t2 = m_useMemory - min(m_useMemory, t2);
		t2 = offset - min(offset, t2);
		if (t2 >= t1)
		{
			t3 = (t2 + m_grain - 1) / m_grain * m_grain;
			if (t3 <= offset) tempFileOffset = t3;
			else 
			{
				t3 = t2 / m_grain * m_grain;
				if (t3 >= t1) tempFileOffset = t3;
				else
				{
					CZ_LOG_ERRORA("");
					return false;
				}
			}
		}
		else
		{
			t3 = (t1 + m_grain - 1) / m_grain * m_grain;
			if (t3 <= offset) tempFileOffset = t3;
			else
			{
				CZ_LOG_ERRORA("");
				return false;
			}
		}
	}
	tempFillSize = (u32)min(m_fileSize - tempFileOffset, m_useMemory);

	// mapping
	if (m_view != NULL)
	{
		UnmapViewOfFile(m_view);
		m_view = NULL;
	}

	high = ((LARGE_INTEGER*)&tempFileOffset)->HighPart;
	low = ((LARGE_INTEGER*)&tempFileOffset)->LowPart;
	m_view = (u8*)MapViewOfFile(m_hMapping, FILE_MAP_READ, high, low, tempFillSize);
	if (m_view == NULL)
	{
		CZ_LOG_ERRORA("");
		return false;
	}

	m_fileOffset = tempFileOffset;
	m_fillSize = tempFillSize;

	return true;
}

bool FileReaderMapping::ReadFileDirect(IN u64 offset, OUT u8* data, INOUT u32& size)
{
	DWORD dwBytesRead;
	u32 t;

	if (SetFilePointerEx(m_hFile, *(LARGE_INTEGER*)&offset, NULL, FILE_BEGIN) == FALSE)
	{
		CZ_LOG_ERRORA("");
		return false;
	}

	t = 0;
	while(true)
	{
		if (ReadFile(m_hFile, data + t, size - t, &dwBytesRead, NULL) == FALSE)
		{
			CZ_LOG_ERRORA("");
			return false;
		}
		if (dwBytesRead == 0) break;
		t += dwBytesRead;
		if (t == size) break;
		if (t > size)
		{
			CZ_LOG_ERRORA("");
			return false;
		}
	}
	size = t;

	return true;
}

bool FileReaderMapping::CheckSize(IN u64 offset1, IN u64 size1, IN u64 offset2, IN u64 size2)
{
	return (!u64AddOverflow(offset2, size2) && (offset2 >= offset1) && ((offset2 + size2) <= (offset1 + size1)));
}